﻿using Dijiang.Errors;
using Dijiang.Values;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Dijiang
{
    public static class FFIFunctions
    {
        public static void Load(CmdEnv env)
        {
            env.Put("load-asm", new DBuiltin(LoadCurrentAssembly));
            env.Put("make-instance", new DBuiltin(MakeInstance));
            env.Put("nullref?", new DBuiltin(IsRefNull));
            env.Put("show-methods", new DBuiltin(ShowMethods));
            env.Put("call-method", new DBuiltin(CallMethod));
        }

        public static object? GetCSharpType(DValue v) => v switch
        {
            DBoolean b => b.value,
            DNumber n => n.num is DNumber.IntNum ? n.IntVal : n.FloatVal,
            DString str => str.value,
            DRef r => r.reference,
            _ => null,
        };

        public static DValue LoadCurrentAssembly(CmdEnv env, DSequence args)
        {
            return new DRef(Assembly.GetExecutingAssembly());
        }

        public static DValue MakeInstance(CmdEnv env, DSequence args)
        {
            if (args.Pop() is not DRef asmRef) return new WrongTypeError(typeof(DRef));
            if (asmRef.reference is not Assembly asm) return new DError("the ref is not " + typeof(Assembly));

            if (args.Pop() is not DString tyName) return new WrongTypeError(typeof(DString));

            var instance = asm.CreateInstance(tyName.value);

            return new DRef(instance);
        }

        public static DValue IsRefNull(CmdEnv env, DSequence args)
        {
            if (args.Pop() is not DRef r) return new WrongTypeError(typeof(DRef));

            return new DBoolean(r.reference == null);
        }

        public static DValue ShowMethods(CmdEnv env, DSequence args)
        {
            if (args.Pop() is not DRef r) return new WrongTypeError(typeof(DRef));

            if (r.reference == null) return new DError("Attempt to call a method of a null obj");
            foreach (var methodInfo in r.reference.GetType().GetMethods())
            {
                Console.WriteLine(methodInfo);
            }

            return new DList();
        }

        public static DValue CallMethod(CmdEnv env, DSequence args)
        {
            if (args.Pop() is not DRef r) return new WrongTypeError(typeof(DRef));
            if (r.reference == null) return new DError("Attempt to call a method of a null obj");

            if (args.Pop() is not DString methodName) return new WrongTypeError(typeof(DString));
            var method = r.reference.GetType().GetMethod(methodName.value);
            if (method == null) return new DError("Can't find method: " + methodName.value);

            return new DRef(method.Invoke(r.reference, args.Select(GetCSharpType).ToArray()));
        }
    }
}
